package cn.sc.summer.construction.user;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.sc.summer.constant.model.PageResult;
import cn.sc.summer.constant.project.DateConstant;
import cn.sc.summer.constant.regex.RegexConstant;
import cn.sc.summer.constant.token.TokenConstant;
import cn.sc.summer.constant.util.AssertUtil;
import cn.sc.summer.constant.util.ConvertUtil;
import cn.sc.summer.core.user.SpringSecurityUserUtil;
import cn.sc.summer.daoapi.dao.SysRoleUserMapper;
import cn.sc.summer.daoapi.dao.SysTenantInfoMapper;
import cn.sc.summer.daoapi.dao.SysUserInfoMapper;
import cn.sc.summer.daoapi.po.SysRoleUser;
import cn.sc.summer.domain.role.SysRoleService;
import cn.sc.summer.domain.route.SysApiService;
import cn.sc.summer.domain.user.SysUserInfoService;
import cn.sc.summer.exception.enums.CommonExceptionEnum;
import cn.sc.summer.exception.exceptions.CommonException;
import cn.sc.summer.feign.vo.UserInfoFeignVO;
import cn.sc.summer.mybatis.annotation.TenantIgnore;
import cn.sc.summer.protocol.form.SysRegistrationForm;
import cn.sc.summer.protocol.form.user.SysUserInfoPageForm;
import cn.sc.summer.protocol.form.user.SysUserInfoUpdateForm;
import cn.sc.summer.protocol.form.user.UserChangePasswordForm;
import cn.sc.summer.protocol.form.user.UserRoleForm;
import cn.sc.summer.protocol.params.SysApiInfo;
import cn.sc.summer.protocol.vo.UserInfoVO;
import cn.sc.summer.redis.util.RedisHelper;
import cn.sc.summer.token.encrypt.TokenUtil;
import cn.sc.summer.token.enums.LoginTypeEnum;
import cn.sc.summer.token.enums.UserTypeEnum;
import cn.sc.summer.token.po.AuthorityX;
import cn.sc.summer.token.po.SysUserInfo;
import cn.sc.summer.token.po.UserDetailX;
import cn.sc.summer.token.util.AESUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 类名：sys-service实现层
 *
 * @author a-xin
 * @date 2023/8/29 20:16
 */
@Slf4j
@Service
public class SysUserInfoServiceImpl implements SysUserInfoService {

    @Value("${user.defaultPassword:Aa@123456789}")
    private String DEFAULT_PASSWORD;

    @Resource
    private SysRoleService sysRoleService;

    @Resource
    private SysApiService sysApiService;

    @Resource
    private SysUserInfoMapper sysUserInfoMapper;

    @Resource
    private SysRoleUserMapper sysRoleUserMapper;

    @Resource
    private SysTenantInfoMapper sysTenantInfoMapper;

    /**
     * 接口: 获取所有用户信息
     *
     * @return List<UserInfo>
     * @author a-xin
     * @date 2023/9/3 13:04
     */
    @Override
    @Cacheable(value={"getAllUsers"})
    public PageResult<UserInfoVO> getAllUsers(SysUserInfoPageForm form) {
        return sysUserInfoMapper.selectPage(form,
                new LambdaQueryWrapper<SysUserInfo>()
                        .like(StrUtil.isNotBlank(form.getPhone()), SysUserInfo::getPhone, form.getPhone()),
                UserInfoVO.class);
    }

    /**
     * 接口: 用户注册WEB
     *
     * @param form 注册表单信息
     * @return cn.sc.summer.daoapi.po.UserInfo
     * @author a-xin
     * @date 2023/9/3 13:11
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public UserInfoVO registrationWeb(SysRegistrationForm form) {
        return registration(form, LoginTypeEnum.WEB);
    }

    /**
     * 接口: 用户注册APP
     *
     * @param form 注册表单信息
     * @return cn.sc.summer.daoapi.po.UserInfo
     * @author a-xin
     * @date 2023/9/3 13:11
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public UserInfoVO registrationApp(SysRegistrationForm form) {
        return registration(form, LoginTypeEnum.APP);
    }

    /**
     * 获取登录用户的api权限信息
     *
     * @param userId    用户ID
     * @param loginType 登录类型
     */
    @Override
    @TenantIgnore
    public Set<AuthorityX> getUserAuth(String userId, String loginType) {
        SysUserInfo sysUserInfo = Optional.ofNullable(sysUserInfoMapper.selectById(userId))
                .orElseThrow(() -> new CommonException("不存在对应的用户信息!"));

        String userType = sysUserInfo.getUserType();
        UserTypeEnum userTypeEnum = Optional.ofNullable(UserTypeEnum.getByType(userType))
                .orElseThrow(() -> new CommonException("用户类型异常!"));

        List<SysApiInfo> apiList = new ArrayList<>();
        switch (userTypeEnum) {
            case SUPER:
                apiList.addAll(sysApiService.getAllApiInfo());
                break;
            case NORMAL:
                List<SysRoleUser> sysRoleUsers =
                        sysRoleUserMapper.selectList(SysRoleUser::getUserId, userId);
                apiList.addAll(sysRoleService.getApiInfoByRoleIds(sysRoleUsers.stream().map(SysRoleUser::getRoleId)
                        .collect(Collectors.toList())));
                break;
        }

        RedisHelper.del(TokenConstant.USER_API + userId);

        List<String> userApiList = new ArrayList<>();
        Set<AuthorityX> authorityXES = apiList.stream().map(allApi -> {
            AuthorityX authorityX = new AuthorityX();
            authorityX.setAuthority(allApi.getApi());
            userApiList.add(allApi.getApi());
            return authorityX;
        }).collect(Collectors.toSet());
        RedisHelper.lAddAll(TokenConstant.USER_API + userId, userApiList);

        return authorityXES;
    }

    /**
     * 用户绑定角色
     *
     * @param form 用户角色信息
     * @return 是否操作成功
     */
    @Override
    public Boolean userBindRole(UserRoleForm form) {

        Optional.ofNullable(sysUserInfoMapper.selectById(form.getUserId()))
                .orElseThrow(() -> new CommonException("不存在对应的用户信息!"));
        List<String> roleIds = form.getRoleIds();
        sysRoleService.checkRole(roleIds);

        List<SysRoleUser> sysRoleUsers =
                sysRoleUserMapper.selectList(SysRoleUser::getUserId, form.getUserId());
        List<String> needBindRoleIds = new ArrayList<>();
        AssertUtil.functionIfPre(sysRoleUsers, CollUtil::isNotEmpty, () -> {
            needBindRoleIds.addAll(sysRoleUsers.stream().map(SysRoleUser::getRoleId)
                    .filter(item -> !roleIds.contains(item))
                    .collect(Collectors.toList()));
        }, () -> {
            needBindRoleIds.addAll(roleIds);
        });

        List<SysRoleUser> sysRoleUserList = needBindRoleIds.stream().map(roleId -> {
            SysRoleUser sysRoleUser = new SysRoleUser();
            sysRoleUser.setRoleId(roleId);
            sysRoleUser.setUserId(form.getUserId());
            return sysRoleUser;
        }).collect(Collectors.toList());
        sysRoleUserMapper.insertBatchSomeColumn(sysRoleUserList);
        return Boolean.TRUE;
    }

    /**
     * 用户取消绑定角色
     *
     * @param form 用户角色信息
     * @return 是否操作成功
     */
    @Override
    public Boolean userUnBindRole(UserRoleForm form) {
        return sysRoleUserMapper.delete(new LambdaQueryWrapper<SysRoleUser>()
                .eq(SysRoleUser::getUserId, form.getUserId())
                .in(SysRoleUser::getRoleId, form.getRoleIds())) > 0;
    }

    /**
     * 接口: feign-根据用户电话查询用户信息
     *
     * @param phone 用户手机号
     * @return cn.sc.summer.daoapi.po.UserInfo 用户信息
     * @author a-xin
     * @date 2023/9/11 11:07
     */
    @Override
    @GlobalTransactional(rollbackFor = Exception.class)
    public UserInfoFeignVO getUserInfoByPhone(String phone) {
        SysUserInfo sysUserInfo = Optional.ofNullable(sysUserInfoMapper.selectOne(new LambdaQueryWrapper<SysUserInfo>()
                .eq(true, SysUserInfo::getPhone, phone))).orElseThrow(() -> new CommonException("不存在对应的用户信息！"));
        return BeanUtil.toBean(sysUserInfo, UserInfoFeignVO.class);
    }

    /**
     * 用户忘记密码
     *
     * @param form 忘记密码提交表单
     * @return 成功与否
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public String forgetPassword(UserChangePasswordForm form) {
        CommonExceptionEnum.NORMAL_EXCEPTION.exceptionIf(!form.getNewPassword1().equals(form.getNewPassword2()), "两次输入的密码不一致！");

        SysUserInfo sysUserInfo = Optional.ofNullable(sysUserInfoMapper.selectOne(new LambdaQueryWrapper<SysUserInfo>()
                        .eq(SysUserInfo::getPhone, form.getPhone())))
                .orElseThrow(() -> new CommonException("该用户不存在！"));

        for (LoginTypeEnum enums : LoginTypeEnum.values()) {
            Set<ZSetOperations.TypedTuple<Object>> webToken = RedisHelper.zGetAllDES(enums.getSlogan() + sysUserInfo.getId());
            AssertUtil.functionIfPre(webToken, CollUtil::isNotEmpty, () -> {
                webToken.stream().forEach(typedTuple -> RedisHelper.del(TokenConstant.ACCESS_TOKEN_PREFIX + typedTuple.getValue()));
                RedisHelper.del(enums.getSlogan() + sysUserInfo.getId());
            });
        }

        String randomKey = RandomUtil.randomString(16);
        sysUserInfo.setPassWord(AESUtil.encrypt(form.getNewPassword1(), randomKey));
        sysUserInfo.setAcesskey(randomKey);
        sysUserInfoMapper.updateById(sysUserInfo);

        RedisHelper.hSet(TokenConstant.USER_INFO + sysUserInfo.getId(), "passWord", sysUserInfo.getPassWord());
        RedisHelper.hSet(TokenConstant.USER_INFO + sysUserInfo.getId(), "acesskey", sysUserInfo.getAcesskey());

        return "用户密码更新成功！";
    }

    /**
     * 用户修改密码
     *
     * @param form 忘记密码提交表单
     * @return 成功与否
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public String changePassword(UserChangePasswordForm form) {

        CommonExceptionEnum.NORMAL_EXCEPTION.exceptionIf(!form.getNewPassword1().equals(form.getNewPassword2()), "两次输入的密码不一致！");

        UserDetailX loginUser = SpringSecurityUserUtil.getLoginUser();
        String loginToken = SpringSecurityUserUtil.getLoginToken();

        SysUserInfo sysUserInfo = Optional.ofNullable(sysUserInfoMapper.selectById(loginUser.getUserId())).orElseThrow(() -> {
            TokenUtil.deleteSimpleToken(loginToken);
            return new CommonException("该用户不存在！");
        });

        for (LoginTypeEnum enums : LoginTypeEnum.values()) {
            Set<ZSetOperations.TypedTuple<Object>> webToken = RedisHelper.zGetAllDES(enums.getSlogan() + sysUserInfo.getId());
            AssertUtil.functionIfPre(webToken, CollUtil::isNotEmpty, () -> {
                webToken.stream().forEach(typedTuple -> RedisHelper.del(TokenConstant.ACCESS_TOKEN_PREFIX + typedTuple.getValue()));
                RedisHelper.del(enums.getSlogan() + sysUserInfo.getId());
            });
        }

        String randomKey = RandomUtil.randomString(16);
        sysUserInfo.setPassWord(AESUtil.encrypt(form.getNewPassword1(), randomKey));
        sysUserInfo.setAcesskey(randomKey);
        sysUserInfoMapper.updateById(sysUserInfo);

        RedisHelper.hSet(TokenConstant.USER_INFO + sysUserInfo.getId(), "passWord", sysUserInfo.getPassWord());
        RedisHelper.hSet(TokenConstant.USER_INFO + sysUserInfo.getId(), "acesskey", sysUserInfo.getAcesskey());

        return "更新成功，请返回重新登陆！";
    }

    /**
     * 获取登录用户信息
     *
     * @return 登录用户信息
     */
    @Override
    public UserInfoVO getUserInfo() {
        return BeanUtil.toBean(SpringSecurityUserUtil.getLoginUser(), UserInfoVO.class);
    }

    /**
     * 删除用户
     *
     * @param id 用户ID
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteUser(String id) {

        SysUserInfo sysUserInfo = Optional.ofNullable(sysUserInfoMapper.selectById(id))
                .orElseThrow(() -> new CommonException("不存在对应的用户信息！"));

        TokenUtil.deleteUserToken(id);
        RedisHelper.del(TokenConstant.USER_INFO + sysUserInfo.getId());
        sysUserInfoMapper.deleteById(sysUserInfo);

    }

    /**
     * 更新当前用户信息
     *
     * @param form 更新提交表单
     * @return 更新后用户信息
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public UserInfoVO updateUser(SysUserInfoUpdateForm form) {
        UserDetailX loginUser = SpringSecurityUserUtil.getLoginUser();
        SysUserInfo sysUserInfo = new SysUserInfo();
        sysUserInfo.setAge(form.getAge())
                .setEmail(form.getEmail())
                .setName(form.getName())
                .setId(loginUser.getUserId());
        sysUserInfoMapper.updateById(sysUserInfo);

        Map<String, Object> userInfoMap = ConvertUtil.convertToMap(sysUserInfo);
        userInfoMap.put("updateTime", DateUtil.format(sysUserInfo.getUpdateTime(), DateConstant.YYYY_MM_DD_HH_MM_SS_1));
        userInfoMap.put("createTime", DateUtil.format(sysUserInfo.getCreateTime(), DateConstant.YYYY_MM_DD_HH_MM_SS_1));
        RedisHelper.hmSet(TokenConstant.USER_INFO + sysUserInfo.getId(), userInfoMap);

        return BeanUtil.toBean(sysUserInfo, UserInfoVO.class);
    }

    /**
     * 获取登录用户路由信息
     *
     * @return 路由树结构
     */
    @Override
    public List<Tree<String>> getUserRoute() {
        return new ArrayList<>();
    }

    @Transactional(rollbackFor = Exception.class)
    public UserInfoVO registration(SysRegistrationForm form, LoginTypeEnum loginTypeEnum) {

        Optional.ofNullable(sysTenantInfoMapper.selectById(form.getDataTenantId()))
                .orElseThrow(() -> new CommonException("不存在对应的区域信息！"));

        Optional.ofNullable(sysUserInfoMapper.selectOne(new LambdaQueryWrapper<SysUserInfo>()
                .eq(true, SysUserInfo::getPhone, form.getPhone()))).ifPresent(userInfo -> {
            throw new CommonException("该手机号已注册！请直接登录！");
        });

        String randomKey = RandomUtil.randomString(16);
        SysUserInfo sysUserInfo = new SysUserInfo();
        BeanUtils.copyProperties(form, sysUserInfo);

        String userId = IdWorker.getIdStr();
        sysUserInfo.setAcesskey(randomKey)
                .setName(form.getName())
                .setPhone(form.getPhone());

        sysUserInfo.setId(userId)
                .setCreateUser(userId)
                .setUpdateUser(userId)
                .setDataTenantId(form.getDataTenantId());

        sysUserInfo.setUserType(CharSequenceUtil.isBlank(form.getUserType()) || form.getUserType().equals(UserTypeEnum.SUPER.getType())
                ? UserTypeEnum.NORMAL.getType()
                : UserTypeEnum.SUPER.getType());

        switch (loginTypeEnum) {
            case APP:
                CommonExceptionEnum.NORMAL_EXCEPTION.predicate(form.getOpenId(), StrUtil::isBlank, "用户openId不能为空！");
                CommonExceptionEnum.NORMAL_EXCEPTION.exceptionIf(sysUserInfoMapper.selectCount(new LambdaQueryWrapper<SysUserInfo>()
                        .eq(SysUserInfo::getOpenId, form.getOpenId())) > 0, "已存在对应OpenId的用户！");

                sysUserInfo.setPassWord(AESUtil.encrypt(DEFAULT_PASSWORD, randomKey));
                sysUserInfo.setOpenId(form.getOpenId());
                sysUserInfo.setRegistType(LoginTypeEnum.APP.getType());
                break;
            case WEB:
                CommonExceptionEnum.NORMAL_EXCEPTION.predicate(form.getPassWord(), StrUtil::isBlank, "密码不能为空！");
                CommonExceptionEnum.NORMAL_EXCEPTION.predicate(form.getPassWord(),
                        passWord -> !passWord.matches(RegexConstant.PASSWORD_STRONG_REGEX),
                        "密码不符合规范：大小写字母+数字+特殊符号+大于10位！");
                sysUserInfo.setPassWord(AESUtil.encrypt(form.getPassWord(), randomKey));
                sysUserInfo.setOpenId(null);
                sysUserInfo.setRegistType(LoginTypeEnum.WEB.getType());
                break;
            case SMS_WEB:
                sysUserInfo.setOpenId(null);
                sysUserInfo.setRegistType(LoginTypeEnum.SMS_WEB.getType());
                break;
            default:
                throw new CommonException("不存在对应的用户类型！");
        }

        //缓存用户信息
        Map<String, Object> userInfoMap = ConvertUtil.convertToMap(sysUserInfo);
        userInfoMap.put("updateTime", DateUtil.format(new Date(), DateConstant.YYYY_MM_DD_HH_MM_SS_1));
        userInfoMap.put("createTime", DateUtil.format(new Date(), DateConstant.YYYY_MM_DD_HH_MM_SS_1));
        RedisHelper.hmSet(TokenConstant.USER_INFO + sysUserInfo.getId(), userInfoMap);

        sysUserInfoMapper.insert(sysUserInfo);

        UserInfoVO userInfoVO = new UserInfoVO();
        BeanUtils.copyProperties(sysUserInfo, userInfoVO);

        return userInfoVO;
    }

}
